package org.lql.chaos.rocketmq.convert;

import com.google.protobuf.InvalidProtocolBufferException;
import com.google.protobuf.MessageLite;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageHeaders;
import org.springframework.messaging.converter.MessageConverter;
import org.springframework.messaging.support.MessageBuilder;
import org.springframework.util.Assert;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: liangqinglong
 * @date: 2025-08-15 00:46
 * @description: Protobuf消息转换器，专门用于处理Google Protobuf消息
 **/
public class ProtobufMessageConvert implements MessageConverter {
    
    private static final Logger log = LoggerFactory.getLogger(ProtobufMessageConvert.class);
    
    // 方法缓存，提高性能
    private final ConcurrentHashMap<Class<?>, Method> parseMethodCache = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<Class<?>, Method> toByteArrayMethodCache = new ConcurrentHashMap<>();

    @Override
    public Object fromMessage(Message<?> message, Class<?> targetClass) {
        if (!(message.getPayload() instanceof byte[]) || !MessageLite.class.isAssignableFrom(targetClass)) {
            log.debug("Cannot convert message to target class: {}", targetClass.getName());
            return null;
        }
        
        try {
            byte[] payload = (byte[]) message.getPayload();
            return parseFrom(payload, targetClass);
        } catch (Exception e) {
            log.error("Failed to parse Protobuf message to target class: {}", targetClass.getName(), e);
            throw new RuntimeException("Protobuf parse error: " + targetClass.getName(), e);
        }
    }

    @Override
    public Message<?> toMessage(Object payload, MessageHeaders headers) {
        try {
            byte[] bytes = toByteArray(payload);
            return (bytes == null) ? null : MessageBuilder.withPayload(bytes).copyHeadersIfAbsent(headers).build();
        } catch (Exception e) {
            log.error("Failed to convert payload to Protobuf message: {}", 
                    payload != null ? payload.getClass().getName() : "null", e);
            throw new RuntimeException("Failed to convert payload to Protobuf message", e);
        }
    }
    
    /**
     * 从字节数组解析Protobuf消息
     * 
     * @param data 字节数组
     * @param targetClass 目标类
     * @return 解析后的对象
     * @throws NoSuchMethodException 找不到方法
     * @throws IllegalAccessException 方法访问异常
     * @throws InvocationTargetException 方法调用异常
     * @throws InvalidProtocolBufferException Protobuf解析异常
     */
    private Object parseFrom(byte[] data, Class<?> targetClass) 
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InvalidProtocolBufferException {
        Assert.notNull(data, "Data must not be null");
        Assert.notNull(targetClass, "Target class must not be null");
        
        Method parseMethod = parseMethodCache.computeIfAbsent(targetClass, cls -> {
            try {
                return cls.getMethod("parseFrom", byte[].class);
            } catch (NoSuchMethodException e) {
                log.warn("No parseFrom method found for class: {}", cls.getName());
                return null;
            }
        });
        
        if (parseMethod == null) {
            throw new NoSuchMethodException("No parseFrom method found for " + targetClass.getName());
        }
        
        return parseMethod.invoke(null, data);
    }
    
    /**
     * 将对象转换为Protobuf字节数组
     * 
     * @param payload 对象
     * @return 字节数组
     * @throws NoSuchMethodException 找不到方法
     * @throws IllegalAccessException 方法访问异常
     * @throws InvocationTargetException 方法调用异常
     */
    private byte[] toByteArray(Object payload) 
            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        if (payload instanceof MessageLite) {
            MessageLite message = (MessageLite) payload;
            Class<? extends MessageLite> messageClass = message.getClass();
            
            Method toByteArrayMethod = toByteArrayMethodCache.computeIfAbsent(messageClass, cls -> {
                try {
                    return cls.getMethod("toByteArray");
                } catch (NoSuchMethodException e) {
                    log.warn("No toByteArray method found for class: {}", cls.getName());
                    return null;
                }
            });
            
            if (toByteArrayMethod == null) {
                throw new NoSuchMethodException("No toByteArray method found for " + messageClass.getName());
            }
            
            return (byte[]) toByteArrayMethod.invoke(message);
        } else if (payload instanceof byte[]) {
            return (byte[]) payload;
        } else {
            return null;
        }
    }
}